其他
安卓应用跳转回流的统一和复用
The following article is from 搜狐技术产品 Author 狐友陈金凤
作为一个功能复杂的应用,无法避免地需要支持众多路径的回流,比如从Launcher、从Push通知、从端外H5、从合作第三方App以及从系统资源分享组件等。
如何解决所有回流入口传入的数据,放到同一个处理入口,方便后续回流的统一管理? 如何约定一套回流协议,让所有回流入口的相似需求,可以复用页面跳转和控制实现? 如何支持回流过程中的条件检测,以及检测中断后的回流恢复? 如何融合回流的复用设计和中断恢复设计?
1回流入口统一
常见业务回流场景
为了更好地理解应用的端外回流的具体业务场景,我用社交应用的回流场景为例说明一下:用户从桌面Launcher打开应用,经过Splash页,最后进入应用MainPage主页; 用户通过端外H5页,下载安装,并打开App。通过启动页后,先登入,再进行个人信息设置、喜好设置、推荐好友、应用MainPage页,最后进入用户主页,或H5活动页等; 用户通过点击系统通知栏的Push通知,打开应用并跳转到Push指定页,如,用户主页、H5活动页、Feed详情页等; 用户浏览器中的端外H5分享页,直接进入到应用内的用户主页,Feed详情页等; 用户从第三方App,通过分享ShareSDK,分享Url和图片到应用中,进入到Feed发布页; 用户从系统分享组件中,分享URL网页到Feed发布页。
统一回流处理入口
上面举例的这么多回流入口,能不能都统一到一个入口去处理呢?这是我们统一回流方案首先要解决的问题。除了系统分享组件回流入口外,其它回流入口,只要和各合作方协商约定好,都是可以通过 sohuhy://xxx 形式的URL Scheme 请求,跳转到App里的。这样,我们可以将所有回流入口都通过这种形式统一到一个Acitivity去处理,比如我们把这个唯一入口命名为ActionActivity,那我们的入口应该像这段代码显示的这样去处理:<activity android:name=".ActionActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="*"
android:scheme="http" />
<data
android:host="*"
android:scheme="https" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="sohuhy" />
</intent-filter>
</activity>
你可以看到,通过ActionActivity,我们能接受系统分享组件入口过来的分享,也可以接受给浏览器发送的URL网页分享,还能支持所有通过sohuhy这个Scheme启动的隐式调用。
我们的Push通知,端外H5,集成ShareSDK的三方应用,都可以通过改用Scheme隐式调用的方式回流到应用里。按照上面的代码配置后,ActionActivity就可以统一处理所有回流入口的数据了,统一处理应该像这段代码显示的这样去处理:public class ActionActivity extends BaseActivity {
private void dispatchIntent() {
mIntent = getIntent();//获取入口数据
if (Intent.ACTION_SEND.equals(mIntent.getAction())) {
matchShare();//如果数据来自Send入口,则进入分享处理逻辑
} else {
String scheme = mIntent.getData().getScheme();
//如果数据来自View入口,则区分scheme进行处理
if (scheme != null && scheme.startsWith("http")) {
matchShare();//如果数据来自http网页分享,则进入分享处理逻辑
} else {
matchProtocol();//如果数据来自sohuhy内部定义回流,则进入内部处理逻辑
}
}
}
}
2回流复用
回流处理的相似性
为了更好理解不同回流入口中相似页面跳转和操作场景,我用社交应用中内容分享的场景举例说明:回流协议定义
而关于回流协议定义,我们遵守了标准的URL格式规范,格式是这样的:scheme://host/path?param1=1¶m2=2使用熟悉的协议规范,可以减少团队学习和使用成本。URL格式作为回流协议,我们需要做一些内部约定,比如这张表格中列出的:回流协议实现
协议定义好了,接下来我们一起来看回流协议如何实现。首先我们的目标是低耦合设计,不同协议的处理是完全隔离的,采用Dispatch派发模式来实现设计需求。首先我们定义协议和处理协议的分发配置类ProtocolConfig:public class BaseProtocolDispatcher implements Serializable {
//所有协议处理类的基类,
public void execute() {
}
}
public class ProtocolConfig { // 所有协议处理类的基类,
public static final String SCHEME = "sohuhy";//协议scheme
public static final String HOST = "w.sohu.com";//协议host
//协议path
public static final String ACTION_MAINPAGE = "mainPage";
public static final String ACTION_BROWSER = "browser";
public static final String ACTION_SHARE_TO_NATIVE = "shareToNative";
......
private static final Map<String, Class> ACTION_MAP_NEW = new HashMap<>();
static {//协议-协议派发器 Map初始化
ACTION_MAP_NEW.put(ACTION_MAINPAGE, OpenMainPageDispatcher
.class);
ACTION_MAP_NEW.put(ACTION_BROWSER, OpenBrowserDispatcher.class);
ACTION_MAP_NEW.put(ACTION_SHARE_TO_NATIVE, ShareFeedDispatcher
.class);
......
}
public static Map.Entry<String, Class> getMatchResult(String url) { //根据协议url 获取 协议派发器
......
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();//根据协议url 解释出scheme
if (!SCHEME.equals(scheme)) {
return null;
}
String host = uri.getHost();//根据协议url 解释出host
if (!HOST.equals(host)) {
return null;
}
List<String> pathList = uri.getPathSegments();
if (pathList != null && pathList.size() == 1) {
path = pathList.get(0);//根据协议url 解释出path, 只支持一级path
}
......
for (Map.Entry<String, Class> entry : ACTION_MAP_NEW.entrySet()) {
try {
String key = entry.getKey();
if (path.equals(key)) return entry;//遍历Map,返回对应派发器
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
return null;
}
public class ProtocolExecutor {//回流协议分发给协议处理类
public static boolean execute(String protocolUrl) {
try {
final Map.Entry<String, Class> result = ProtocolConfig.getMatchResult(protocolUrl);//获取匹配的协议处理类
if (result != null) {
final Class clazz = result.getValue();
ProtocolBaseDispatcher dispatcher = null;
try {
dispatcher = (ProtocolBaseDispatcher) clazz.newInstance();//通过反射得到协议处理对象
} catch (Exception e) {
}
Uri uri = Uri.parse(protocolUrl);
fillFields(dispatcher, clazz, uri);//通过反射将协议参数填充到对应变量中
dispatcher.execute();//执行协议处理类
return true;
} else {
//对于不识别协议path,说明是新增的回流协议,做升级提示
//对于空协议path,应用已经被系统唤起,不需要额外处理,直接退出
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
// OpenBrowser协议对应的处理实现类
public class OpenBrowserDispatcher extends BaseProtocolDispatcher {
public String url;//会通过反射将协议参数赋值到实例对象中
public int fullScreen;//会通过反射将协议参数赋值到实例对象中
@Override
public void execute() {//协议处理实现方法
super.execute(url);
//通过WebView打开端外H5页面
ActivityModel.toNewWebViewActivity(mContext, url, fullScreen);
}
}
//ActionActivity统一入口中调用协议处理模块
public class ActionActivity extends BaseActivity {
private void matchProtocol()() {
mProtocolUrl = mIntent.getData();//获取协议串
ProtocolExecutor.excute(mProtocolUrl.toString());//协议处理
}
}
通过ProtocolConfig查找对应的协议处理类,并通过反射实例化对象; 通过反射,将协议中参数值赋值到协议处理对象对应的变量中; 调用处理类的处理流程,处理类根据协议要求执行相应操作,如跳转到新的H5活动页,进行内容分享、打开用户主页进行关注等。
通过将协议protocolUrl,映射到处理类及其成员变量,就很好地实现了回流协议的分发,达到了我们低耦合高内聚的协议隔离,和回流复用的设计目标。
3回流中段恢复
回流的条件检测场景
在应用的回流过程中往往需要进行一些条件检测,比如,需要强制登入,需要强制升级等场景。为了让你更好地理解回流条件检测具体是个什么样的业务,我用社交应用的回流中断场景举例说明。回流的中断恢复设计
我们观察上面的回流条件检测场景,可以发现这种流线式的条件检测,特别像一个节点链。我们可以采用责任链的模式进行处理。也就是说每个节点负责一个条件的检测,只有上一个条件检测通过后才会继续下一个条件检测。我们用Checker来表示一个检测点,可以按如下代码进行处理:public class BaseChecker implements Parcelable {//检测节点基础类
//执行回流条件检测,子类需要重写此类
protected void dispatch() {
}
//Checker检测执行入口
public void execute(){
dispatch();
}
}
public class UpgradeChecker extends BaseChecker{//升级检测节点
@Override
protected void dispatch() {
if (UpGradeManager.getInstance().shouldShowUpdate()) {//如果用户需要强制升级,则跳转到升级页面
UpGradeManager.getInstance().update(mContext, info.mData);
} else {//否则,则进入最后一个检测节点BaseChecker
super.dispatch();
}
}
}
public class RecommendUserChecker extends UpgradeChecker{//推荐检测节点
@Override
protected void dispatch() {
int guidPage = SPUtil.getInstance().getInt(GuideStep.getGuideKey(), GuideStep.GUIDE_UNDEFINE);
if (guidPage > GuideStep.GUIDE_CLOUDTAG) {//如果用户已经完成推荐关注,则进入下一个检测节点UpgradeChecker
super.dispatch();
} else {// 否则,跳转到推荐关注页
ActivityModel.toRecommendUserActivity(params);
}
}
}
public class CloudTagChecker extends RecommendChecker{//兴趣标签检测节点
@Override
protected void dispatch() {
int guidPage = SPUtil.getInstance().getInt(GuideStep.getGuideKey(), GuideStep.GUIDE_UNDEFINE);
if (guidPage > GuideStep.GUIDE_CLOUDTAG) {//如果用户已经完成标签选择,则进入下一个检测节点RecommendChecker
super.dispatch();
} else {// 否则,跳转到标签选择页
ActivityModel.toCloudTagActivity(params);
}
}
}
public class NewUserGuideChecker extends CloudTagChecker{//新用户资料检测节点
@Override
protected void dispatch() {
int guidPage = SPUtil.getInstance().getInt(GuideStep.getGuideKey(), GuideStep.GUIDE_UNDEFINE);
if (guidPage > GuideStep.GUIDE_NEWUSERGUIDE) {//如果用户已经完成资料设置,则进入下一个检测节点CloudTagChecker
super.dispatch();
} else {//否则,跳转到资料设置页
ActivityModel.toNewUserGuideActivity(params);
}
}
}
public class LoginChecker extends NewUserGuideChecker {//登入检测节点
@Override
protected void dispatch() {
if (UserModel.getInstance().isLogin()) {//如果用户已经登入,则进入下一个检测节点NewUserGuideChecker
super.dispatch();
} else {// 否则,跳转到登入界面
ActivityModel.toLoginActivity(params);
}
}
}
public class SplashChecker extends LoginChecker{//启动页检测节点
@Override
protected void dispatch() {
if (!HyApp.getIsAppShow()) {//如果应用没有显示,先展示启动页
ActivityModel.toSplashActivity(params);
}else{//否则,继续下一个检测节点LoginChecker的条件检测
super.dispatch();
}
}
}
public class ActionActivity extends BaseActivity{//回流入口
@Override
protected void matchProtocol() {//启动第一个检测点SplashChecker
new SplashChecker().execute();
}
}
public class CheckerActivity extends BaseActivity{//条件检测界面基类
private BaseChecker restoreChecker; //待回复检测点
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//存储待回复检测点
restoreChecker = (BaseChecker)getIntent().getParcelableExtra(BaseChecker.KEY_CHECKER);
}
protected void restoreChecker() {//恢复下一个检测点
if(restoreChecker != null) {
restoreChecker.execute();
return true;
}
return false;
}
}
public class SplashActivity extends CheckerActivity{
private void toNextActivity() {//Splash展示完成后,优先恢复检测点
if(restoreChecker()){
finish();
} else {//没有需要恢复的检测点,直接进入到应用主页
ActivityModel.toMainPageActivity(params);
}
}
}
public class SplashChecker extends LoginChecker{
@Override
protected void dispatch() {
if (!HyApp.getIsAppShow()) {//开屏页导致流程中断时,把登入检测点传给Splash页面
params.putParcelableExtra(BaseChecker.KEY_CHECKER, new LoginChecker.setUri(mProtocolUri));
ActivityModel.toSplashActivity(params);
}else{
super.dispatch();
}
}
public class LoginActivity extends CheckerActivity{
private void loginSuccess() {//登入成功后,优先恢复下一个检测点
if(restoreChecker()){
finish();
} else {//没有需要恢复的检测点,直接进入到应用主页
ActivityModel.toMainPageActivity(params);
}
}
}
public class LoginChecker extends NewUserGuideChecker {
@Override
protected void dispatch() {
if (UserModel.getInstance().isLogin()) {
super.dispatch();
} else {//登入导致流程中断时,把新用户资料检测点传给Login页面
params.putParcelableExtra(BaseChecker.KEY_CHECKER, this)
ActivityModel.toLoginActivity(params);
}
}
}
回流中断恢复设计和复用设计的融合
回流时的条件检测都通过后,我们需要继续进行回流的目标页跳转和相应操作,这就需要我们把回流中断恢复设计和复用设计很好地融合起来。我们可以利用中断检测的基类BaseChecker来保存回流协议,以及恢复协议执行,如下代码所示:public class BaseChecker implements Parcelable {//检测节点基础类
//用于Intent传递Checker的key定义
public static final String KEY_CHECKER = "key_checker";
//检测完后,需要恢复执行的回流协议
protected Uri mProtocolUri;
public Dispatcher setUri(Uri protocolUri) {//保存需要恢复执行的回流协议
mProtocolUri = protocolUri;
return this;
}
//BaseChecker作为最后一个节点, 用来负责恢复执行回流协议
protected void dispatch() {
String protocolUrl = mProtocolUri.toString();
ProtocolExecutor.excute(protocolUrl);//衔接上回流复用设计
}
}
public class ActionActivity extends BaseActivity{//回流入口
@Override
protected void matchProtocol() {//启动第一个检测点SplashChecker,并传递后续要恢复的回流协议mProtocolUri用于后续恢复
new SplashChecker().setUri(mProtocolUri).execute();
}
}
完成条件检测链和中断回流恢复的设计,我们页就完成回流的整体方案设计。
4总结
最后我们一起来总结一下整个回流方案的设计。整个方案通过ActionActivity对端外回流入口的进行统一处理; 通过定义一套合理的跳转协议,为不同回流入口相似功能的复用,建立一个基石; 采用协议解释器ProtocolExecutor,和Dispatch派发模式实现不同协议的解耦处理; 当需要条件检测时,通过继承方式的责任链模式,串联众多的条件检测; 使用CheckerActivity作为各中断界面的基类,处理下一检测节点的保存与恢复; 检测链检测通过后,通过检测基类BaseChecker对回流协议进行存储和恢复,最后通过协议解释器ProtocolExecutor对回流协议进行分发和逻辑处理。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
一个大型 Android 项目的模块划分哲学
细嗅蔷薇,Gradle 系列之 Task 必知必会
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!